﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Bonjour;
using System.Runtime.InteropServices;

namespace AirTunesStreamer
{
    public class RAOPFinder : IDisposable
    {
        private DNSSDEventManager _eventManager = null;
        private DNSSDService _service = null;
        private DNSSDService _browser = null;
        
        private HashSet<BrowseData> _cache = new HashSet<BrowseData>();
        private bool _autoResolve;

        public event Action<string, string> Resolved;
        public event Action<string> Found;
        public event Action<string> Lost;


        public RAOPFinder(Action<string, string> resolved, Action<string> lost)
        {
            _autoResolve = true;
            Init(null, resolved, lost);
        }

        public RAOPFinder(Action<string> found, Action<string, string> resolved, Action<string> lost)
        {
            Init(found, resolved, lost);
        }

        private void Init(Action<string> found, Action<string, string> resolved, Action<string> lost)
        {
            Found = found;
            Lost = lost;
            Resolved = resolved;

            _eventManager = new DNSSDEventManager();
            _eventManager.ServiceFound += ServiceFound;
            _eventManager.ServiceLost += ServiceLost;
            _eventManager.OperationFailed += OperationFailed;

            _service = new DNSSDService();

            _browser = _service.Browse(0, 0, "_raop._tcp", null, _eventManager);
        }

        private void ServiceFound(DNSSDService sref, DNSSDFlags flags, uint ifIndex, string serviceName, string regType, string domain)
        {
            BrowseData data = new BrowseData(ifIndex, serviceName, regType, domain);

            if (Found != null)
            {
                Found(serviceName);
            }


            if (_autoResolve)
            {
                Resolve(data);
            }
            else
            {
                _cache.Add(data);
            }
        }

        private void ServiceLost(DNSSDService sref, DNSSDFlags flags, uint ifIndex, String serviceName, String regType, String domain)
        {
            BrowseData data = new BrowseData(ifIndex, serviceName, regType, domain);
            _cache.Remove(data);
            if (Lost != null)
                Lost(serviceName);
        }

        private void OperationFailed(DNSSDService sref, DNSSDError error)
        {
            Console.WriteLine("Operation failed: error code: " + error, "Error");
        }

        public class Pair<T, K>
        {
            public T First { get; set; }
            public K Second { get; set; }
        }

        public void Resolve(string serviceName)
        {
            BrowseData data = _cache.FirstOrDefault(d => d.Name == serviceName);
            if (data != null)
            {
                Resolve(data);
            }
        }

        private void Resolve(BrowseData data)
        {
            DNSSDEventManager em = new DNSSDEventManager();
            Pair<DNSSDService, _IDNSSDEvents_ServiceResolvedEventHandler> t = new Pair<DNSSDService, _IDNSSDEvents_ServiceResolvedEventHandler>();
            _IDNSSDEvents_ServiceResolvedEventHandler aDelegate = delegate(DNSSDService sref, DNSSDFlags flags, uint ifIndex, String fullName, String hostName, ushort port, TXTRecord txtRecord)
            {
                t.First.Stop();
                em.ServiceResolved -= t.Second;
                if (Resolved != null)
                    Resolved(data.Name, hostName);
            };
            em.ServiceResolved += aDelegate;
            t.Second = aDelegate;
            try
            {
                t.First = _service.Resolve(0, data.InterfaceIndex, data.Name, data.Type, data.Domain, em);
            }
            catch (COMException e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        public void Dispose()
        {
            _eventManager.ServiceFound -= ServiceFound;
            _eventManager.ServiceLost -= ServiceLost;
            _eventManager.OperationFailed -= OperationFailed;
            try
            {
                if (_browser != null)
                {
                    _browser.Stop();
                }

                if (_service != null)
                {
                    _service.Stop();
                }
            }
            catch { }
        }

    }
}
